<?php

declare(strict_types=1);

namespace app\service\order;

use app\model\Order;
use app\model\OrderItem;
use app\model\OrderPayment;
use app\model\OrderRefund;
use app\model\Prod;
use app\model\Org;
use app\model\User;
use app\service\notify\CreateNotifyService;
use app\service\order\AssistAssignService;
use app\util\GenNoUtil;
use InvalidArgumentException;
use think\facade\Db;
use think\Paginator;

class OrderService
{
    public function __construct(
        private CreateNotifyService $notifyService,
        private AssistAssignService $assistAssignService,
        private ShipmentService $shipmentService
    )
    {
    }

    public function paginate(array $filters, int $page, int $pageSize): Paginator
    {
        $query = Order::with(['items', 'payments', 'refunds'])
            ->order('id', 'desc');

        if (!empty($filters['keyword'])) {
            $keyword = trim((string) $filters['keyword']);
            $query->where(function ($subQuery) use ($keyword) {
                $subQuery->whereLike('order_no', '%' . $keyword . '%')
                    ->whereOr('buyer_remark', 'like', '%' . $keyword . '%')
                    ->whereOr('seller_remark', 'like', '%' . $keyword . '%');
            });
        }

        foreach (['status', 'pay_status', 'ship_status', 'refund_status', 'finish_status', 'audit_status'] as $field) {
            if ($filters[$field] ?? '' !== '') {
                $query->where($field, (int) $filters[$field]);
            }
        }

        foreach (['buyer_org_id', 'seller_org_id', 'assist_user_id'] as $field) {
            if ($filters[$field] ?? '' !== '') {
                $query->where($field, (int) $filters[$field]);
            }
        }

        if (!empty($filters['start_time']) && !empty($filters['end_time'])) {
            $query->whereBetweenTime('create_time', $filters['start_time'], $filters['end_time']);
        }

        return $query->paginate([
            'list_rows' => $pageSize,
            'page' => $page,
        ]);
    }

    public function findById(int $id): ?Order
    {
        return Order::with(['items', 'payments', 'refunds'])->find($id);
    }

    public function create(array $payload): Order
    {
        $items = $payload['items'] ?? [];
        if (!is_array($items) || $items === []) {
            throw new InvalidArgumentException('订单明细不能为空');
        }

        $data = $this->extractOrderPayload($payload, true);
        if (empty($data['assist_user_id'])) {
            $assigned = $this->assistAssignService->assign($data['region_id'] ?? null);
            if ($assigned) {
                $data['assist_user_id'] = $assigned;
            }
        }
        $data = $this->ensureRelations($data);

        $itemsData = $this->prepareItems($items);
        $summary = $this->calculateAmounts($itemsData, $data);

        $orderNo = $payload['order_no'] ?? $this->generateOrderNo();
        $this->assertOrderNoUnique($orderNo);
        $data['order_no'] = $orderNo;
        $data = array_merge($data, $summary);

        $status = $data['status'] ?? Order::STATUS_PENDING_PAYMENT;
        if ((int) ($data['pay_status'] ?? 0) === 1) {
            $status = !empty($data['assist_user_id'])
                ? Order::STATUS_PENDING_LOGISTICS
                : Order::STATUS_PENDING_ASSIGN;
        }
        $data['status'] = $status;

        /** @var Order $order */
        $order = Db::transaction(function () use ($data, $itemsData) {
            /** @var Order $order */
            $order = Order::create($data);

            foreach ($itemsData as $item) {
                $item['order_id'] = $order->id;
                OrderItem::create($item);
            }

            return $order;
        });

        $order = $this->findById((int) $order->id);
        if ($order) {
            [$payStatusChanged, $assistChanged] = $this->refreshOrderLifecycle(
                $order,
                (int) ($payload['pay_status'] ?? 0),
                (int) ($payload['assist_user_id'] ?? 0)
            );
            if ($payStatusChanged || ($assistChanged && (int) $order->pay_status === 1)) {
                $this->notifyAssistForPayment($order);
            }
        }

        return $order;
    }

    public function update(Order $order, array $payload): Order
    {
        $data = $this->extractOrderPayload($payload, false);
        if (empty($data['assist_user_id']) && !$order->assist_user_id) {
            $assigned = $this->assistAssignService->assign($data['region_id'] ?? $order->region_id);
            if ($assigned) {
                $data['assist_user_id'] = $assigned;
            }
        }
        $data = $this->ensureRelations($data, (int) $order->buyer_user_id, (int) $order->seller_org_id);

        $originalPayStatus = (int) $order->pay_status;
        $originalAssistId = (int) ($order->assist_user_id ?? 0);

        if (isset($payload['order_no'])) {
            $orderNo = trim((string) $payload['order_no']);
            if ($orderNo === '') {
                throw new InvalidArgumentException('订单号不能为空');
            }
            if ($orderNo !== $order->order_no) {
                $this->assertOrderNoUnique($orderNo, (int) $order->id);
            }
            $data['order_no'] = $orderNo;
        }

        $items = $payload['items'] ?? null;

        Db::transaction(function () use ($order, $data, $items) {
            $order->save($data);

            if (is_array($items)) {
                $prepared = $this->prepareItems($items);
                OrderItem::where('order_id', $order->id)->delete();
                foreach ($prepared as $item) {
                    $item['order_id'] = $order->id;
                    OrderItem::create($item);
                }

                $summary = $this->calculateAmounts($prepared, $order->toArray());
                $order->save($summary);
            }
        });

        $updated = $this->findById((int) $order->id);

        if ($updated) {
            [$payStatusChanged, $assistChanged] = $this->refreshOrderLifecycle(
                $updated,
                $originalPayStatus,
                $originalAssistId
            );

            if ($payStatusChanged || ($assistChanged && (int) $updated->pay_status === 1)) {
                $this->notifyAssistForPayment($updated);
            }
        }

        return $updated;
    }

    public function delete(Order $order): bool
    {
        return (bool) Db::transaction(function () use ($order) {
            OrderItem::where('order_id', $order->id)->delete();
            OrderPayment::where('order_id', $order->id)->delete();
            OrderRefund::where('order_id', $order->id)->delete();
            return $order->delete();
        });
    }

    public function format(Order $order): array
    {
        return [
            'id' => (int) $order->id,
            'order_no' => $order->order_no,
            'buyer_user_id' => (int) $order->buyer_user_id,
            'buyer_org_id' => (int) $order->buyer_org_id,
            'seller_user_id' => $order->seller_user_id ? (int) $order->seller_user_id : null,
            'seller_org_id' => (int) $order->seller_org_id,
            'region_id' => $order->region_id ? (int) $order->region_id : null,
            'assist_user_id' => $order->assist_user_id ? (int) $order->assist_user_id : null,
            'source_channel' => $order->source_channel,
            'status' => (int) $order->status,
            'status_label' => $order->status_label,
            'pay_status' => (int) $order->pay_status,
            'ship_status' => (int) $order->ship_status,
            'finish_status' => (int) $order->finish_status,
            'refund_status' => (int) $order->refund_status,
            'audit_status' => (int) $order->audit_status,
            'audit_remark' => $order->audit_remark,
            'audit_time' => $order->audit_time,
            'buyer_payable_amount' => (float) $order->buyer_payable_amount,
            'buyer_paid_amount' => (float) $order->buyer_paid_amount,
            'seller_receivable_amount' => (float) $order->seller_receivable_amount,
            'platform_commission_amount' => (float) $order->platform_commission_amount,
            'shipping_fee' => (float) $order->shipping_fee,
            'discount_amount' => (float) $order->discount_amount,
            'buyer_remark' => $order->buyer_remark,
            'seller_remark' => $order->seller_remark,
            'internal_remark' => $order->internal_remark,
            'amount_snapshot' => $order->amount_snapshot ?? [],
            'paid_at' => $order->paid_at,
            'shipped_at' => $order->shipped_at,
            'finished_at' => $order->finished_at,
            'cancelled_at' => $order->cancelled_at,
            'create_time' => $order->create_time,
            'update_time' => $order->update_time,
            'items' => array_map(static function (OrderItem $item) {
                return [
                    'id' => (int) $item->id,
                    'product_id' => (int) $item->product_id,
                    'sku_id' => $item->sku_id ? (int) $item->sku_id : null,
                    'quantity' => (int) $item->quantity,
                    'unit_price' => (float) $item->unit_price,
                    'subtotal' => (float) $item->subtotal_amount,
                    'product_snapshot' => $item->product_snapshot ?? [],
                    'create_time' => $item->create_time,
                    'update_time' => $item->update_time,
                ];
            }, $order->items ? $order->items->all() : []),
            'payments' => array_map(static function (OrderPayment $payment) {
                return [
                    'id' => (int) $payment->id,
                    'method' => $payment->pay_method ?? '',
                    'amount' => (float) ($payment->pay_amount ?? 0),
                    'pay_time' => $payment->pay_time ?? null,
                    'pay_data' => $payment->pay_data ?? [],
                ];
            }, $order->payments ? $order->payments->all() : []),
            'refunds' => array_map(static function (OrderRefund $refund) {
                return [
                    'id' => (int) $refund->id,
                    'refund_no' => $refund->refund_no ?? '',
                    'amount' => (float) ($refund->refund_amount ?? $refund->approved_amount ?? 0),
                    'status' => (int) $refund->status,
                    'status_label' => OrderRefund::labelForStatus((int) $refund->status),
                    'apply_amount' => (float) ($refund->apply_amount ?? 0),
                    'approved_amount' => (float) ($refund->approved_amount ?? 0),
                    'buyer_amount' => (float) ($refund->buyer_amount ?? 0),
                    'seller_amount' => (float) ($refund->seller_amount ?? 0),
                    'platform_amount' => (float) ($refund->platform_amount ?? 0),
                    'reason' => $refund->reason ?? '',
                    'evidence' => $refund->evidence ?? [],
                    'approve_time' => $refund->approve_time ?? null,
                    'complete_time' => $refund->complete_time ?? null,
                ];
            }, $order->refunds ? $order->refunds->all() : []),
        ];
    }

    public function formatCollection(iterable $orders): array
    {
        $results = [];
        foreach ($orders as $order) {
            if ($order instanceof Order) {
                $results[] = $this->format($order);
            }
        }

        return $results;
    }

    private function extractOrderPayload(array $payload, bool $isCreate): array
    {
        $fields = [
            'buyer_user_id',
            'buyer_org_id',
            'seller_user_id',
            'seller_org_id',
            'region_id',
            'assist_user_id',
            'source_channel',
            'status',
            'pay_status',
            'ship_status',
            'finish_status',
            'refund_status',
            'audit_status',
            'shipping_fee',
            'discount_amount',
            'buyer_remark',
            'seller_remark',
            'internal_remark',
            'audit_remark',
            'audit_time',
            'paid_at',
            'shipped_at',
            'finished_at',
            'cancelled_at',
        ];

        $data = array_intersect_key($payload, array_flip($fields));

        foreach (['buyer_user_id', 'buyer_org_id', 'seller_user_id', 'seller_org_id', 'region_id', 'assist_user_id'] as $idField) {
            if (array_key_exists($idField, $data) && $data[$idField] !== null && $data[$idField] !== '') {
                $data[$idField] = (int) $data[$idField];
            }
        }

        foreach (['status', 'pay_status', 'ship_status', 'finish_status', 'refund_status', 'audit_status'] as $statusField) {
            if (array_key_exists($statusField, $data) && $data[$statusField] !== null && $data[$statusField] !== '') {
                $data[$statusField] = (int) $data[$statusField];
            }
        }

        foreach (['shipping_fee', 'discount_amount'] as $amountField) {
            if (array_key_exists($amountField, $data) && $data[$amountField] !== null && $data[$amountField] !== '') {
                $data[$amountField] = (float) $data[$amountField];
            }
        }

        if ($isCreate) {
            $data += [
                'status' => $data['status'] ?? 0,
                'pay_status' => $data['pay_status'] ?? 0,
                'ship_status' => $data['ship_status'] ?? 0,
                'finish_status' => $data['finish_status'] ?? 0,
                'refund_status' => $data['refund_status'] ?? 0,
                'audit_status' => $data['audit_status'] ?? 0,
                'shipping_fee' => $data['shipping_fee'] ?? 0,
                'discount_amount' => $data['discount_amount'] ?? 0,
            ];
        }

        return $data;
    }

    /**
     * 校验并补全订单关联的买家/卖家账号信息。
     *
     * @param array $data 待保存的订单数据
     * @param int|null $originalBuyerUserId 旧订单买家用户，用于更新场景
     * @param int|null $originalSellerOrgId 旧订单卖家组织，用于更新场景
     */
    private function ensureRelations(array $data, ?int $originalBuyerUserId = null, ?int $originalSellerOrgId = null): array
    {
        $buyerUserId = $data['buyer_user_id'] ?? $originalBuyerUserId;
        if (!$buyerUserId || User::where('id', $buyerUserId)->count() === 0) {
            throw new InvalidArgumentException('买家用户不存在');
        }
        $data['buyer_user_id'] = $buyerUserId;

        $buyerOrgId = $data['buyer_org_id'] ?? null;
        if (!$buyerOrgId || Org::where('id', $buyerOrgId)->count() === 0) {
            throw new InvalidArgumentException('买家组织不存在');
        }

        $sellerOrgId = $data['seller_org_id'] ?? $originalSellerOrgId;
        if (!$sellerOrgId || Org::where('id', $sellerOrgId)->count() === 0) {
            throw new InvalidArgumentException('卖家组织不存在');
        }
        $data['seller_org_id'] = $sellerOrgId;

        if (isset($data['seller_user_id']) && $data['seller_user_id']) {
            if (User::where('id', $data['seller_user_id'])->count() === 0) {
                throw new InvalidArgumentException('卖家用户不存在');
            }
        }

        if (isset($data['assist_user_id']) && $data['assist_user_id']) {
            if (User::where('id', $data['assist_user_id'])->count() === 0) {
                throw new InvalidArgumentException('客服用户不存在');
            }
        }

        return $data;
    }

    private function prepareItems(array $items): array
    {
        $result = [];
        foreach ($items as $index => $item) {
            if (!is_array($item)) {
                throw new InvalidArgumentException('订单明细格式不正确');
            }

            $productId = (int) ($item['product_id'] ?? 0);
            $quantity = (int) ($item['quantity'] ?? 0);
            $unitPrice = (float) ($item['unit_price'] ?? 0);

            if ($productId <= 0 || $quantity <= 0) {
                throw new InvalidArgumentException('订单明细第 ' . ($index + 1) . ' 行数据不正确');
            }

            if (Prod::where('id', $productId)->count() === 0) {
                throw new InvalidArgumentException('商品不存在: ' . $productId);
            }

            $result[] = [
                'product_id' => $productId,
                'sku_id' => isset($item['sku_id']) ? (int) $item['sku_id'] : null,
                'quantity' => $quantity,
                'unit_price' => $unitPrice,
                'subtotal_amount' => round($unitPrice * $quantity, 2),
                'product_snapshot' => $item['product_snapshot'] ?? [],
            ];
        }

        return $result;
    }

    private function calculateAmounts(array $items, array $orderData): array
    {
        $itemsTotal = array_reduce($items, static function ($carry, $item) {
            return $carry + (float) $item['subtotal_amount'];
        }, 0.0);

        $shippingFee = isset($orderData['shipping_fee']) ? (float) $orderData['shipping_fee'] : 0.0;
        $discount = isset($orderData['discount_amount']) ? (float) $orderData['discount_amount'] : 0.0;

        $payable = max($itemsTotal + $shippingFee - $discount, 0);

        return [
            'amount_snapshot' => [
                'items_total' => round($itemsTotal, 2),
                'shipping_fee' => round($shippingFee, 2),
                'discount_amount' => round($discount, 2),
                'payable' => round($payable, 2),
            ],
            'buyer_payable_amount' => round($payable, 2),
            'buyer_paid_amount' => $orderData['buyer_paid_amount'] ?? 0,
            'seller_receivable_amount' => round($payable - ($orderData['platform_commission_amount'] ?? 0), 2),
            'platform_commission_amount' => $orderData['platform_commission_amount'] ?? 0,
        ];
    }
    private function assertOrderNoUnique(string $orderNo, ?int $excludeId = null): void
    {
        $query = Order::where('order_no', $orderNo);
        if ($excludeId !== null) {
            $query->where('id', '<>', $excludeId);
        }

        if ($query->count() > 0) {
            throw new InvalidArgumentException('订单号已存在');
        }
    }

    private function generateOrderNo(): string
    {
        return GenNoUtil::order();
    }

    /**
     * 调整订单状态，确保付款与客服指派后的状态落位
     *
     * @return array{0: bool, 1: bool} [是否新付款, 是否新指派客服]
     */
    private function refreshOrderLifecycle(Order $order, int $originalPayStatus, int $originalAssistId): array
    {
        $payStatusChanged = $originalPayStatus !== 1 && (int) $order->pay_status === 1;
        $assistChanged = $originalAssistId !== (int) ($order->assist_user_id ?? 0) && (int) ($order->assist_user_id ?? 0) > 0;
        $shouldEnsurePickup = (int) $order->pay_status === 1;

        if ($payStatusChanged) {
            $order->status = empty($order->assist_user_id)
                ? Order::STATUS_PENDING_ASSIGN
                : max($order->status, Order::STATUS_PENDING_LOGISTICS);
        } elseif ($assistChanged && (int) $order->pay_status === 1 && $order->status < Order::STATUS_PENDING_LOGISTICS) {
            $order->status = Order::STATUS_PENDING_LOGISTICS;
        }

        if ($order->status < Order::STATUS_PENDING_PAYMENT && (int) $order->pay_status === 0) {
            $order->status = Order::STATUS_PENDING_PAYMENT;
        }

        $order->save();

        if ($shouldEnsurePickup) {
            $refreshed = $order->refresh();
            if ($refreshed instanceof Order) {
                $this->shipmentService->ensurePickupTask($refreshed);
            }
        }

        return [$payStatusChanged, $assistChanged];
    }

    private function notifyAssistForPayment(Order $order): void
    {
        $targets = [];
        if ($order->assist_user_id) {
            $targets[] = [
                'id' => (int) $order->assist_user_id,
                'type' => 'assistant',
            ];
        }

        if ($targets === []) {
            return;
        }

        $this->notifyService->add($targets, 'order_paid', [
            'order_no' => $order->order_no,
            'buyer_org_id' => $order->buyer_org_id,
            'seller_org_id' => $order->seller_org_id,
            'amount' => $order->buyer_paid_amount,
        ]);
    }
}
